package org.jhove2.module.assess;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import org.junit.Ignore;
import org.junit.Test;
import org.mvel2.MVEL;
/**
* This class is used to test and document know problems with the MVEL library
* that is used by assessment. I have added an @Ignore annotation so these
* tests will not be run during normal times.
*/
@Ignore
public class MvelTest {
@Test
public void testMvelVersion() {
assertEquals("2.0.18", MVEL.VERSION);
}
// This test is failing because of a bug in MVEL which has been reported
@Test
public void testIntegerEvaluations() {
// Maximum size of an integer is 2147483647 [0x7fffffff]
Integer myInteger = new Integer(Integer.MAX_VALUE);
assertEquals(2147483647, myInteger.intValue());
// However MVEL converts this string to a Long
// (see org.mvel2.util.ParseTools.numericTest
// which returns LONG if string length > 9)
Object mvelObject = MVEL.eval("2147483647");
assertEquals("java.lang.Long", mvelObject.getClass().getName());
// Boolean comparison of the Integer object's value
// to the string value returns FALSE !!! WRONG !!!.
boolean result = MVEL.evalToBoolean("intValue() == 2147483647", myInteger);
assertTrue(result);
// Why does the comparison return FALSE? because...
// a debugging trace shows the following sequence
// MVEL.evalToBoolean(String, Object) line: 678
// MVEL.eval(String, Object, Class<T>) line: 218
// MVELInterpretedRuntime.parse() line: 45
// MVELInterpretedRuntime.parseAndExecuteInterpreted() line: 133
// MVELInterpretedRuntime(AbstractParser).arithmeticFunctionReduction(int) line: 2473
// MVELInterpretedRuntime(AbstractParser).reduce() line: 2508
// ExecutionStack.op(int) line: 166
// MathProcessor.doOperations(Object, int, Object) line: 45
// MathProcessor.doOperations(int, Object, int, int, Object) line: 79
// MathProcessor._doOperations(int, Object, int, int, Object) line: 155
//
// Here is the full method signature with argument names
// MathProcessor._doOperations(int type1, Object val1, int operation, int type2, Object val2)
//
// At this point the method argument values are:
// int type1 = 106 (DataTypes.W_INTEGER)
// Object val1 = 2147483647 (java.lang.Integer)
// int operation = 18 (Operator.EQUAL)
// int type2 = 107 (DataTypes.W_LONG)
// Object val2 = 2147483647 (java.lang.Long)
//
// The _doOperations method makes the following call:
// return doBigDecimalArithmetic(getInternalNumberFromType(val1, type1),
// operation,
// getInternalNumberFromType(val2, type2),
// true, box(type2) > box(type1) ? box(type2) : box(type1));
//
// Contained in the above are 2 calls to getInternalNumberFromType(Object, int)
// The first of these has the argument values:
// Object in = 2147483647 (java.lang.Integer)
// int type = 106 (DataTypes.W_INTEGER)
//
// Which causes the following constructor call to be made:
// return new InternalNumber((Integer) in, MathContext.DECIMAL32);
//
// InternalNumber calls its super class constructor:
// public BigDecimal(int val, MathContext mc) {
// intCompact = val;
// if (mc.precision > 0)
// roundThis(mc);
// }
//
// The value of mc.precision is 7 for MathContext.DECIMAL32
//
// Therefore there is a call to BigDecimal.roundThis(MathContext mc),
// which in turn calls BigDecimal.doRound(BigDecimal d, MathContext mc),
// which sets:
// intVal = null
// intCompact = 2147484
// scale = -3
// precision = 10
//
// What the hell is going on here? Why is the integer being manipulated in this way?
//
// The second call to getInternalNumberFromType results in the following:
// return new InternalNumber((Long) in, MathContext.DECIMAL64);
//
// The value of mc.precision is 16 for MathContext.DECIMAL64
// which yields the following settings for the BigDecimal that is created:
// intVal = null
// intCompact = 2147483647
// scale = 0
// precision = 10
//
// Now that the BigDecimals have been created, MVEL can finally enter the
// MathProcessor.doBigDecimalArithmetic method which compares like this:
// case EQUAL:
// return val1.compareTo(val2) == 0 ? Boolean.TRUE : Boolean.FALSE;
//
// I am having trouble following the logic at this point,
// but the bottom line is that the comparison returns FALSE,
// which is totally unexpected.
}
@Test
public void testCompareLong() {
// Create a Long object
Long myLong = new Long(987654321);
// MVEL can correctly retrieve the long value
Object mvelValue = MVEL.eval("longValue()",myLong);
assertEquals(myLong.longValue(), ((Long) mvelValue).longValue());
// MVEL correctly compares value if long constant is explicitly typed with "L"
boolean result1 = MVEL.evalToBoolean("longValue() == 987654321L", myLong);
assertTrue(result1);
// comparison fails if the constant is not explicitly typed
boolean result2 = MVEL.evalToBoolean("longValue() == 987654321", myLong);
assertFalse(result2);
}
@Test
public void testIsdef() {
// Create a Long object
Long myLong = new Long(987654321);
// The following statement throws an exception
boolean result = MVEL.evalToBoolean("isdef longValue()", myLong);
assertTrue(result);
/*
This is the sequence of calls being made
MVEL.evalToBoolean(String, Object) line: 678
MVEL.eval(String, Object, Class<T>) line: 218
MVELInterpretedRuntime.parse() line: 45
MVELInterpretedRuntime.parseAndExecuteInterpreted() line: 94
IsDef.getReducedValue(Object, Object, VariableResolverFactory) line: 34
Here is the getReducedValue method:
public Object getReducedValue(Object ctx, Object thisValue, VariableResolverFactory factory) {
return factory.isResolveable(nameCache) || (thisValue != null && getFieldOrAccessor(thisValue.getClass(), nameCache) != null);
}
A NullPointerException is being raised because the factory variable is null
*/
}
}